<!--
  全波谱去卷积 GSD
 -->
 
<!--
  视图层
-->
<template><header-comp />

<!-- 功能简介 -->
<uni-card
  title="功能简介"
  :is-full="false"
  :border="true"
  :is-shadow="true"
>
  <!-- 通告栏 -->
  <uni-notice-bar text=
    "To新手：
    　　读取数据后，直接去最下面点“绘图预览”。
    　　再根据谱图情况，重点微调“GSD调参”下的“杂峰因子”。
    Tips：
    　　原谱图数据质量越差，杂峰因子越要调高点（建议先做基线校正）。"
  />
  <!-- 文字 -->
  <view>
    　　全波谱去卷积（Global Spectra Deconvolution, GSD）。<br />
    　　GSD算法是对波谱去卷积（Spectral Deconvolution, SD）算法的进一步发展，其可对整个波谱谱图进行标峰、谱线平滑、峰型优化等操作，是波谱处理领域的常规方法。<br />
    　　标峰/平滑原理：使用导数法识别峰（即区域极小值/极大值），因此要求谱图Y轴数据关于X连续可导，即：①要求X轴数据单调连续；②需运用广义的Savitzky-Golay（S-G）算法对谱图进行平滑处理，以防止导数剧烈波动。
  </view>
  <!-- 二级标题：参考文献 -->
  <uni-section
    title="参考文献"
    type="line"
    :padding="true"
  >
    <!-- 文字内容：文献 -->
    <view>
      　　文献链接：
    </view>
    <!-- 链接：文献 -->
    <uni-link
      text="https://doi.org/10.3103/S0146411619050055"
      href="https://doi.org/10.3103/S0146411619050055"
      class="center" color="blue"
    />
    <!-- 文字内容：库 -->
    <view>
      　　库链接：
    </view>
    <!-- 链接：库 -->
    <uni-link
      text="https://www.npmjs.com/package/ml-gsd"
      href="https://www.npmjs.com/package/ml-gsd"
      class="center" color="blue"
    />
  </uni-section>
</uni-card>

<!-- 读取数据 -->
<uni-card
  title="读取数据"
  :is-full="false"
  :border="true"
  :is-shadow="true"
>
  <!-- 通告栏 -->
  <uni-notice-bar text=
    "打开含有波谱数据集的Excel文件。"
  />

  <!-- 二级标题：快速开始 -->
  <uni-section
    v-if="!isGetXlsxFile"
    title="快速开始"
    type="circle"
    padding="0 1em"
  >
    <!-- 文字内容 -->
    <view><text>
      　　一键读取示例数据，快速体验功能。
    </text></view>
    <!-- 读取示例数据按钮 -->
    <view class="center">
      <button
        @click="readExampleData()"
        size="mini" type="primary"
      >
        快速读取示例数据
      </button>
    </view>
  </uni-section>

  <!-- 二级标题：下载示例数据文件 -->
  <uni-section
    title="示例数据下载"
    type="circle"
    padding="0 1em"
  >
    <!-- 文字内容 -->
    <view><text>
      　　操作步骤：\n
      　　① 下载示例Excel模板文件。\n
      　　② 根据模板格式，将你自己的数据拷贝至模板。\n
      　　③ 保存模板文件，然后直接点“点击按钮读取数据文件”读取你的数据文件即可。
    </text></view>
    <!-- 示例文件下载按钮 -->
    <view class="center">
      <button
        @click="downloadExampleFile()"
        size="mini" type="primary"
      >
        示例Excel文件下载
      </button>
    </view>
  </uni-section>

  <!-- 二级标题：读取数据文件 -->
  <uni-section
    title="读取数据"
    type="circle"
    padding="0 1em"
  >
    <!-- 文件上传容器 -->
    <uni-file-picker
      title="点击按钮读取数据文件："
      file-extname="xlsx, xls" fileMediatype="all"
      limit="1" return-type="object" :del-icon="false"
      :auto-upload="false" :disable-preview="true"
      @select="(event) => readDataFile(event)"
    />
  </uni-section>

  <!-- 二级标题：数据形状 -->
  <!-- 条件渲染：得到数据才能渲染 -->
  <uni-section
    v-if="isGetXlsxFile"
    title="数据形状"
    type="circle"
    padding="0 1em"
  >
    <!-- 文字内容 -->
    <view><text>
      　　需确定是“正置谱图”还是“倒置谱图”。\n
      　　紫外光谱为典型的正置谱图；红外光谱为典型的倒置谱图。\n
      　　默认“正置”。
    </text></view>
    <!-- 数据形状 -->
    <view class="center">
      倒置　<switch
        :checked="isMaxCriteria"
        @change="(event) => { isMaxCriteria = event.detail.value }"
        type="switch"
      />　正置
    </view>
  </uni-section>
  
</uni-card>

<!-- 数据预处理。条件渲染：有数据才渲染 -->
<data-prep-comp
  v-if="isGetXlsxFile"
  :isMinToZero="dataPrepProps.isMinToZero"
  :isMinZoom="dataPrepProps.isMinZoom"
  :isMaxToOne="dataPrepProps.isMaxToOne"
  :changeValueFunc="dataPrepProps.changeValueFunc"
/>

<!-- 折叠面板。条件渲染：有数据才渲染 -->
<uni-collapse v-if="isGetXlsxFile">

<!-- GSD运算调参 -->
<uni-card title="GSD运算调参">
  <!-- 通告栏 -->
  <uni-notice-bar text=
    "对数据进行全波谱去卷积处理的一些设置项。"
  />
  <!-- 下拉框容器 -->
  <uni-collapse-item title="下拉查看设置详情">

    <!-- 二级标题：预平滑 -->
    <uni-section
      title="预平滑"
      type="circle"
      padding="0 1em"
    >
      <!-- 文字内容 -->
      <view><text>
        　　先对Y轴数据进行一次S-G平滑处理，再进行标峰等操作。\n
        　　会对峰、谷的强度产生影响，进而对噪音阈值和杂峰排除因子产生影响。建议专业用户结合“S-G算法调参”进行微调优化。\n
        　　默认“关闭”。
      </text></view>
      <!-- 是否打开 -->
      <view class="center">
        关闭　<switch
          :checked="smoothY"
          @change="(event) => { smoothY = event.detail.value }"
          type="switch"
        />　开启
      </view>
    </uni-section>

    <!-- 二级标题：噪音阈值 -->
    <uni-section
      title="噪音阈值"
      type="circle"
      padding="0 1em"
    >
      <!-- 文字内容 -->
      <view><text>
        　　设置一个绝对值（数据预处理归一化后为0~1之间的数值），低于该值的数值均被认为是噪音。\n
        　　默认值为：单个波谱内所有Y数据的绝对中位差MAD + 1.5 * 标准差SD。
      </text></view>
      <!-- 是否打开 -->
      <view class="center">
        默认值　<switch
          :checked="isNoiseLevelSet"
          @change="(event) => { isNoiseLevelSet = event.detail.value }"
          type="switch"
        />　自定义
      </view>
      <!-- 输入框的表格容器 -->
      <uni-table
        :border="true" :stripe="true"
      >
        <!-- 标题行 -->
        <uni-tr>
          <uni-th align="center">
            噪音阈值
          </uni-th>
        </uni-tr>
        <!-- 内容行 -->
        <uni-tr>
          <uni-td>
            <uni-easyinput
              :disabled="!isNoiseLevelSet"
              v-model="noiseLevel"
              type="number"
              placeholder="请输入噪音阈值"
              trim="all" :clearable="false"
            />
          </uni-td>
        </uni-tr>
      </uni-table>
    </uni-section>

    <!-- 二级标题：杂峰因子 -->
    <uni-section
      title="杂峰因子"
      type="circle"
      padding="0 1em"
    >
      <!-- 文字内容 -->
      <view><text>
        　　设定一个杂峰因子，用于计算杂峰阈值，低于杂峰阈值的峰会被认为是杂峰，不被标峰。\n
        　　杂峰阈值 = y(min) + [y(max) - y(min)] * 杂峰因子\n
        　　如果设置的噪音阈值大于y(min)，那么y(min)就取噪音阈值。\n
        　　默认值为25%。
      </text></view>
      <!-- 输入框的表格容器 -->
      <uni-table
        :border="true" :stripe="true"
      >
        <!-- 标题行 -->
        <uni-tr>
          <uni-th align="center">
            杂峰因子(%)
          </uni-th>
        </uni-tr>
        <!-- 内容行 -->
        <uni-tr>
          <uni-td>
            <uni-easyinput
              v-model="minMaxRatio"
              type="number"
              placeholder="请输入杂峰因子"
              trim="all" :clearable="false"
            />
          </uni-td>
        </uni-tr>
      </uni-table>
    </uni-section>

    <!-- 二级标题：峰位微调 -->
    <uni-section
      title="峰位微调"
      type="circle"
      padding="0 1em"
    >
      <!-- 文字内容 -->
      <view><text>
        　　在得到峰位置数据后，默认认为峰形状符合对数函数(log10)，选取以峰坐标为中心的左右各2个（共5个）相邻数据点，利用对数函数插值方法，计算出峰可能的更精确的位置。\n
        　　需确定峰形状确实符合对数函数(log10)，否则可能会失真。\n
        　　默认“关闭”。
      </text></view>
      <!-- 是否打开 -->
      <view class="center">
        关闭　<switch
          :checked="realTopDetection"
          @change="(event) => { realTopDetection = event.detail.value }"
          type="switch"
        />　开启
      </view>
    </uni-section>

  </uni-collapse-item>
</uni-card>

<!-- S-G算法调参 -->
<uni-card title="S-G算法调参">
  <!-- 通告栏 -->
  <uni-notice-bar text=
    "GSD在平滑求导这一步引用了Savitzky-Golay算法，即：在数据点的局部邻域内，使用最小二乘法拟合一个多项式。
    局部邻域选取多少数据点？多项式的最大阶数是多少？可供专业人士调参。"
  />
  <!-- 下拉框容器 -->
  <uni-collapse-item title="下拉查看设置详情">

    <!-- 二级标题：局部邻域点数量 -->
    <uni-section
      title="局部邻域点数量"
      type="circle"
      padding="0 1em"
    >
      <!-- 文字内容 -->
      <view><text>
        　　以将要平滑处理的数据点为中心，选取其左右一定数量的领域点，用于拟合。\n
        　　局部邻域点数量需为奇数，以确保中心点左右的领域点数量相同。\n
        　　局部邻域点过少，则无法降低谱线噪音的剧烈波动，最小不能低于5，否则程序会报错。\n
        　　局部邻域点过大，则将显著增加运算开销，最大不能超过数据样本数量，否则程序会报错。\n
        　　默认为“9”，即左右各4个点，共9个点参与拟合。
      </text></view>
      <!-- 输入框的表格容器 -->
      <uni-table
        :border="true" :stripe="true"
      >
        <!-- 标题行 -->
        <uni-tr>
          <uni-th align="center">
            局部邻域点数量
          </uni-th>
        </uni-tr>
        <!-- 内容行 -->
        <uni-tr>
          <uni-td>
            <uni-easyinput
              v-model="windowSize"
              type="number"
              placeholder="请输入局部邻域点数量"
              trim="all" :clearable="false"
            />
          </uni-td>
        </uni-tr>
      </uni-table>
    </uni-section>

    <!-- 二级标题：多项式阶数 -->
    <uni-section
      title="多项式阶数"
      type="circle"
      padding="0 1em"
    >
      <!-- 文字内容 -->
      <view><text>
        　　用于拟合平滑的多项式的阶数，即拟合方程(y ~ x)中x最大可取多少次方。\n
        　　多项式的阶数一般不会大于局部邻域点数量的一半。\n
        　　默认为“3”。
      </text></view>
      <!-- 输入框的表格容器 -->
      <uni-table
        :border="true" :stripe="true"
      >
        <!-- 标题行 -->
        <uni-tr>
          <uni-th align="center">
            多项式的阶数
          </uni-th>
        </uni-tr>
        <!-- 内容行 -->
        <uni-tr>
          <uni-td>
            <uni-easyinput
              v-model="polynomial"
              type="number"
              placeholder="请输入多项式的阶数"
              trim="all" :clearable="false"
            />
          </uni-td>
        </uni-tr>
      </uni-table>
    </uni-section>

  </uni-collapse-item>
</uni-card>

<!-- 数据输出 -->
<uni-card title="数据输出">
  <!-- 通告栏 -->
  <uni-notice-bar text=
    "生成的波谱数据的参数设置。"
  />
  <!-- 下拉框容器 -->
  <uni-collapse-item title="下拉查看设置详情">

    <!-- 二级标题：输出范围 -->
    <uni-section
      title="输出范围"
      type="circle"
      padding="0 1em"
    >
      <!-- 文字内容 -->
      <view><text>
        　　输出的波长/波数/位移值的范围。\n
        　　默认为第一组数据的范围。
      </text></view>
      <!-- 输入框的表格容器 -->
      <uni-table
        :border="true" :stripe="true"
      >
        <!-- 标题行 -->
        <uni-tr>
          <uni-th align="center">
            起始值
          </uni-th>
          <uni-th align="center">
            终止值
          </uni-th>
        </uni-tr>
        <!-- 内容行 -->
        <uni-tr>
          <uni-td>
            <uni-easyinput
              v-model="outputFrom"
              type="number"
              placeholder="请输入起始波长/波数/位移值"
              trim="all" :clearable="false"
            />
          </uni-td>
          <uni-td>
            <uni-easyinput
              v-model="outputTo"
              type="number"
              placeholder="请输入终止波长/波数/位移值"
              trim="all" :clearable="false"
            />
          </uni-td>
        </uni-tr>
      </uni-table>
    </uni-section>
    
    <!-- 二级标题：输出精度 -->
    <uni-section
      title="输出精度"
      type="circle"
      padding="0 1em"
    >
      <!-- 文字内容 -->
      <view><text>
        　　在输出范围内，均匀取多少个数据点以生成输出谱图数据。\n
        　　数据点个数的计算方法为：(终止值 - 起始值) / 分辨率 + 1\n
        　　默认为第一组数据的数据点个数。\n
      </text></view>
      <!-- 输入框的表格容器 -->
      <uni-table
        :border="true" :stripe="true"
      >
        <!-- 标题行 -->
        <uni-tr>
          <uni-th align="center">
            输出数据点个数
          </uni-th>
        </uni-tr>
        <!-- 内容行 -->
        <uni-tr>
          <uni-td>
            <uni-easyinput
              v-model="outputPointNumber"
              type="number"
              placeholder="请填写输出数据点的个数"
              trim="all" :clearable="false"
            />
          </uni-td>
        </uni-tr>
      </uni-table>
    </uni-section>

  </uni-collapse-item>
</uni-card>
 
<!-- 结果栏 -->
<uni-card title="结果栏">
  <!-- 通告栏 -->
  <uni-notice-bar text=
    "预览操作的结果，并导出结果。
    可反复修改参数预览，最后导出自己满意的结果。"
  />

  <!-- 二级标题：结果预览 -->
  <uni-section
    title="结果预览"
    type="circle"
    padding="0 1em"
  >
    <!-- 文字内容 -->
    <view><text>
      　　用第一组波谱数据进行试处理，并对处理结果进行绘图预览。\n
      　　会同时生成原标峰谱图，及Gaussian、Lorentzian、pseudo-Voigt方程拟合得到的谱图，用于比较评估。
    </text></view>
    <!-- 绘图预览相关按钮容器 -->
    <view class="center">
      <!-- 绘图预览按钮 -->
      <button
        @click="previewResult()"
        size="mini" type="primary"
      >
        绘图预览
      </button>
      <!-- 打开图表板按钮 -->
      <button
        @click="showVisor()"
        size="mini" type="primary"
      >
        打开图表板
      </button>
    </view>
  </uni-section>

  <!-- 二级标题：波谱线型 -->
  <uni-section
    title="波谱线型"
    type="circle"
    padding="0 1em"
  >
    <!-- 文字内容 -->
    <view><text>
      　　导出数据用。可选Gaussian方程、Lorentzian方程、pseudo-Voigt方程(Gaussian、Lorentzian的加权方程)中的一种对原始标峰数据进行峰值拟合，得到拟合后的波谱线型。\n
      　　峰值拟合会丢失标峰后的部分数据信息，且可能造成数据失真，但也有文献指出峰值拟合后的波谱数据往往拥有更高的数据质量和分析精度。见仁见智。\n
      　　默认为“不进行拟合”。
    </text></view>
    <!-- 选择器容器 -->
    <uni-data-checkbox
      v-model="optimizedKind"
      :localdata="optimizedKinds"
      class="center"
    />
  </uni-section>

  <!-- 二级标题：数据批量处理 -->
  <uni-section
    title="数据批量处理"
    type="circle"
    padding="0 1em"
  >
    <!-- 文字内容 -->
    <view><text>
      　　根据各项设置，批量处理全部波谱数据，并将数据结果导出为Excel文件。\n
      　　“原格式”即每一列为一组数据，和示例数据格式一致。\n
      　　“数据矩阵”即每一行为一组数据，符合矩阵/张量格式，便于深度学习。
    </text></view>
    <!-- 执行操作按钮 -->
    <view class="center">
      <button
        @click="resultToDownload(true)"
        size="mini" type="primary"
      >
        原格式导出
      </button>
      <button
        @click="resultToDownload(false)"
        size="mini" type="primary"
      >
        数据矩阵导出
      </button>
    </view>
  </uni-section>

</uni-card>

</uni-collapse>

<!-- 页脚(下) -->
<footer-comp /></template>

<!-- 逻辑层 -->
<script setup>
// 导入vue的onMounted、ref方法
import { shallowRef } from "vue"
// 导入数据预处理组件
import dataPrepComp from "../index/data-prep-comp.vue"
// 封装一个数据预处理组件的传参对象
const dataPrepProps = {
  isMinToZero: false,
  isMinZoom: false,
  isMaxToOne: false,
  changeValueFunc: (key, value) => {
    dataPrepProps[key] = value
  }
}
// 导入tfjs-visor库
// import * as tf from "@tensorflow/tfjs"
// import * as tfvis from "@tensorflow/tfjs-vis"
// 导入xlsx库
import * as XLSX from "xlsx"
// 导入ml库的gsd、optimizePeaks方法
import { gsd, optimizePeaks } from "ml-gsd"
// 导入spectrum-generator库的SpectrumGenerator构造方法
import { SpectrumGenerator } from "spectrum-generator"
// 导入公共方法库的方法
import {
  // 下载文件
  downloadFile,
  // 数组转置
  arrayTranspose,
  // 打开图表栏
  showVisor,
  // 下载示例文件
  downloadExampleFile,
  // 读取XLSX文件为工作簿对象
  readXlsxFile,
  // 从工作表提取原始数据
  sheetToDataArr,
  // 用tfvis渲染Aoa数据为图表
  aoaTotfvisChart,
  // 数据归一化
  minToZero, maxToOne
} from "../../scripts/app_common.js"
// 导入示例数据集
import { exampleData } from "../../datasets/example-data.js"

/**
 * @ref对象
 */
// 是否读取到数据的boolean对象
const isGetXlsxFile = shallowRef(null)
// 谱图正置或倒置的boolean对象
const isMaxCriteria = shallowRef(true)
// 预平滑的boolean对象
const smoothY = shallowRef(false)
// 是否开启噪音阈值设置的boolean对象
const isNoiseLevelSet = shallowRef(false)
// 噪音阈值设置的number对象
const noiseLevel = shallowRef(0)
// 杂峰因子的number对象（需要除以100）
const minMaxRatio = shallowRef(25)
// 峰位微调的boolean对象
const realTopDetection = shallowRef(false)
// S-G算法的局部邻域尺寸和多项式阶数2个number对象
const windowSize = shallowRef(9)
const polynomial = shallowRef(3)
// 输出范围及精度的3个number对象
const outputFrom = shallowRef(null)
const outputTo = shallowRef(null)
const outputPointNumber = shallowRef(null)
// 光谱峰型优化类型的1个string对象
const optimizedKind = shallowRef("raw")

/**
 * @其他对象
 */
// 峰值拟合的选项内容
const optimizedKinds = [
  {text: "不进行峰值拟合", value: "raw"},
  {text: "Gaussian拟合", value: "gaussian"},
  {text: "Lorentzian拟合", value: "lorentzian"},
  {text: "pseudo-Voigt拟合", value: "pseudoVoigt"}
]
// 原始数据整理后得到的数据数组
let dataJsonArr = null


/**
 * @readDataFile 读取数据文件
 * @function async
 * @param { Event } event 点击事件
 * 读取文件并转置，然后调用文件数据检查修复方法solveRawData()
 * 成功后绘图，并修改isGetXlsxFile状态
 */
async function readDataFile(event) { try {
  // 显示加载圈
  uni.showLoading({ title: "正在读取文件数据" })
  // 先清零dataJsonArr数据
  dataJsonArr = null
  // 从event中获取工作簿对象
  const workbook = await readXlsxFile(event.tempFiles[0].file)
  // 将工作簿第1张表转为Json化的数据数组
  dataJsonArr = sheetToDataArr(workbook.Sheets[workbook.SheetNames[0]])
  // 如果数据检查成功通过，接到返回数据
  if (dataJsonArr) {
    // 赋值输出谱图形状ref对象
    outputFrom.value = dataJsonArr[0].x[0]
    outputPointNumber.value = dataJsonArr[0].x.length
    outputTo.value = dataJsonArr[0].x[dataJsonArr[0].x.length - 1]
    // 拿第一个数据作图
    aoaTotfvisChart(
      // 图表类型
      "scatterplot",
      // 图表容器
      { tab: "GSD", name: "原始数据(第一组)" },
      // 图表数据：第一个数据
      [dataJsonArr[0]],
      // 数据序列标签
      ["原始数据"]
    )
    // 操作结束，isGetXlsxFile的ref对象标记为true
    isGetXlsxFile.value = true
    // 隐藏加载圈
    uni.hideLoading()
  }
} catch (error) {
  console.error("readDataFile()报错: ", error)
  uni.hideLoading()
  uni.showModal({
    showCancel: false,
    title: "读取数据文件报错",
    content: error,
  })
}}

/**
 * @readExampleData 直接读取示例数据
 * @function
 */
function readExampleData() { try {
  // 把示例数据组装成AOA形式，直接赋值给dataJsonArr即可
  dataJsonArr = [exampleData]
  // 赋值输出谱图形状ref对象
  outputFrom.value = dataJsonArr[0].x[0]
  outputPointNumber.value = dataJsonArr[0].x.length
  outputTo.value = dataJsonArr[0].x[dataJsonArr[0].x.length - 1]
  // 拿第一个数据作图
  aoaTotfvisChart(
    // 图表类型
    "scatterplot",
    // 图表容器
    { tab: "GSD", name: "原始数据(示例)" },
    // 图表数据：第一个数据
    [dataJsonArr[0]],
    // 数据序列标签
    ["原始数据"]
  )
  // 打开图表栏
  showVisor()
  // 操作结束，isGetXlsxFile的ref对象标记为true
  isGetXlsxFile.value = true
} catch (error) {
  console.error("readExampleData()报错: ", error)
  uni.showModal({
    showCancel: false,
    title: "读取示例数据报错",
    content: error,
  })
}}

/**
 * @dataProcess 数据处理，相当于本业务内部的公共方法
 * 会读取页面ref对象的参数，对数据进行处理
 * @function
 * @param {{
 *   x: Number[],
 *   y: Number[],
 *   header: String | Number
 * }} dataJson 单组数据对象
 * @returns {{
 *   peaksRaw: GSDPeakID[],
 *   dataJsonDeepCopy: {
 *     x: Number[],
 *     y: Number[],
 *     header: String | Number
 *   }
 * }} 标峰的数组，深拷贝的数据对象
 */
function dataProcess(dataJson) { try {
  // 深拷贝dataJson对象
  const dataJsonDeepCopy = JSON.parse(JSON.stringify(dataJson))
  // 预处理：零位校正
  if (dataPrepProps.isMinToZero) {
    minToZero(dataJsonDeepCopy.y, dataPrepProps.isMinZoom, isMaxCriteria.value)
  }
  // 预处理：上限归一
  if (dataPrepProps.isMaxToOne) {
    maxToOne(dataJsonDeepCopy.y, isMaxCriteria.value)
  }
  // 预处理作图
  if (dataPrepProps.isMinToZero || dataPrepProps.isMaxToOne) {
    aoaTotfvisChart(
      // 图表类型
      "scatterplot",
      // 图表容器
      { tab: "GSD", name: "原始数据(归一化)" },
      // 图表数据：第一个数据
      [dataJsonDeepCopy],
      // 数据序列标签
      ["归一化"]
    )
  }
  // GSD处理
  const peaksRaw = gsd(dataJsonDeepCopy, {
    sgOptions: {
      // 窗口大小和多项式阶数须为整数
      windowSize: Math.round(windowSize.value),
      polynomial: Math.round(polynomial.value)
    },
    smoothY: smoothY.value,
    isMaxCriteria: isMaxCriteria.value,
    noiseLevel: (isNoiseLevelSet.value) ?
      Number(noiseLevel.value) : undefined,
    minMaxRatio: minMaxRatio.value / 100,
    realTopDetection: realTopDetection.value
  })
  console.log("标峰数据: ", peaksRaw)
  // 处理完毕，返回峰数据
  return {
    peaksRaw: peaksRaw,
    dataJsonDeepCopy: dataJsonDeepCopy
  }
} catch (error) {
  console.error("dataProcess()报错: ", error)
  uni.showModal({
    showCancel: false,
    title: "数据运算报错",
    content: error,
  })
}}

/**
 * @generateSpectrum 读取标峰数据，生成波谱数据
 * 也会读取页面ref对象的波谱生成参数
 * @function
 * @param { GSDPeakID[] } peaks 标峰的数组
 * @returns { DataXY<Float64Array> } 波谱数据
 */
function generateSpectrum(peaks) { try {
  // 波谱构造器
  const spectrumGenerator = new SpectrumGenerator({
    from: Number(outputFrom.value),
    to: Number(outputTo.value),
    nbPoints: Number(outputPointNumber.value),
    shape: { kind: "pseudoVoigt" }
  })
  // 添加峰（peaks对象自带x, y, width三个传参）
  spectrumGenerator.addPeaks(peaks)
  // 生成波谱数据
  const spectrum = spectrumGenerator.getSpectrum()
  // 返回数据
  return spectrum
} catch (error) {
  console.error("generateSpectrum()报错: ", error)
  uni.showModal({
    showCancel: false,
    title: "生成波谱数据报错",
    content: error,
  })
}}

/**
 * @previewResult 绘图预览
 * @function
 * @note 直接或间接读取页面ref参数，串联所有操作方法
 */
function previewResult() { try {
  // 显示加载圈
  uni.showLoading({ title: "正在处理数据，请稍候..." })
  // 用第一条数据简单处理一下
  const dataProcessed = dataProcess(dataJsonArr[0])
  // 搞个峰数组，用于预览，先放一个原始峰
  const previewPeaksArr = [dataProcessed.peaksRaw]
  // 遍历峰优化选项(跳过原始峰那个选项)
  for (let i = 1; i < optimizedKinds.length; i++) {
    // 把可选的拟合峰都推进previewPeaksArr数组里
    previewPeaksArr.push(optimizePeaks(
      dataProcessed.dataJsonDeepCopy,
      dataProcessed.peaksRaw,
      { shape: { kind: optimizedKinds[i].value } }
    ))
  }
  // 依次生成波谱数据并作图
  for (let i = 0; i < previewPeaksArr.length; i++) {
    // 依次生成波谱对象
    const spectrum = generateSpectrum(previewPeaksArr[i])
    // 图的标签
    const chartLabel = (previewPeaksArr[i][0]) ? (
      (previewPeaksArr[i][0].shape) ?
        (previewPeaksArr[i][0].shape.kind + "拟合")
        : ("无拟合")
      ) : ("Null")
    // 绘线型图预览
    aoaTotfvisChart(
      // 图表类型
      "scatterplot",
      // 图表容器
      { tab: "GSD", name: chartLabel },
      // 图表数据：第一个数据
      [spectrum],
      // 数据序列标签
      [chartLabel]
    )
  }
  // 最后，打开图表版呈现数据
  showVisor()
  // 隐藏加载圈
  uni.hideLoading()
} catch (error) {
  console.error("previewResult()报错: ", error)
  uni.hideLoading()
  uni.showModal({
    showCancel: false,
    title: "结果预览报错",
    content: error,
  })
}}

/**
 * @resultToDownload 干到结果并下载
 * 执行处理操作，结果作为Excel表格下载
 * @function
 * @param { Boolean } [isArrayTranspose] 导出的Excel文件是否转置。转置即为原格式；不转置即为张量格式。
 * @note 直接或间接读取页面ref参数，串联所有操作方法
 */
function resultToDownload(isArrayTranspose) { try {
  // 显示加载圈
  uni.showLoading({ title: "处理中..." })
  // 建个全部光谱数据的数组spectrumPointArr
  const spectrumPointArr = []
  // 看一看要不要峰值优化
  if (optimizedKind.value !== "raw") {
    // 要峰值优化，先接一个谱图数据
    const dataProcess0 = dataProcess(dataJsonArr[0])
    // 峰优化
    const spectrum = generateSpectrum(optimizePeaks(
      dataProcess0.dataJsonDeepCopy,
      dataProcess0.peaksRaw,
      { shape: { kind: optimizedKind.value } }
    ))
    // 把X和Y推进去
    spectrumPointArr.push(Array.from(spectrum.x), Array.from(spectrum.y))
    // 然后依次遍历处理除第一个以外的所有数据，只推Y
    for (let i = 1; i < dataJsonArr.length; i++) {
      const dataProcessI = dataProcess(dataJsonArr[i])
      // 峰值优化后生成谱图，Y丢进spectrumPointArr数组
      spectrumPointArr.push(Array.from(
        generateSpectrum(optimizePeaks(
          dataProcessI.dataJsonDeepCopy,
          dataProcessI.peaksRaw,
          { shape: { kind: optimizedKind.value } }
        )).y)
      )
    }
  // 如果不需要优化
  } else {
    // 不做峰值优化，也先接一个不优化的谱图数据
    const spectrum = generateSpectrum(dataProcess(dataJsonArr[0]).peaksRaw)
    // 把X和Y推进去
    spectrumPointArr.push(Array.from(spectrum.x), Array.from(spectrum.y))
    // 然后依次遍历处理除第一个外的所有数据，只推Y
    for (let i = 1; i < dataJsonArr.length; i++) {
      // 直接生成谱图，Y丢进spectrumPointArr数组
      spectrumPointArr.push(Array.from(
        generateSpectrum(dataProcess(dataJsonArr[i]).peaksRaw).y
      ))
    }
  }
  // 生成Excel数据，先接收标题
  const headerArr = ["X"]
  for (let i = 0; i < dataJsonArr.length; i++) {
    headerArr.push(dataJsonArr[i].header)
  }
  // 若要求转置，即生成原格式
  if (isArrayTranspose) {
    // 转置Aoa，含标题
    const outputAoa = [headerArr, ...arrayTranspose(spectrumPointArr)]
    // 生成表格
    // aoa_to_sheet()方法接受Array-of-Array对象，FLoat64Array不行
    // 如遇FLoat64Array，一定要提前用Array.from()转成普通Array
    const sheet = XLSX.utils.aoa_to_sheet(outputAoa)
    // 创建一个新的工作簿对象
    const workbook = XLSX.utils.book_new()
    // 将工作表添加到工作簿，该表名为"波谱数据集"
    XLSX.utils.book_append_sheet(workbook, sheet, "波谱数据集_处理")
    // 以工作簿生成ArrayBuffer对象
    const workbookArrBufObj = XLSX.write(workbook, { type: "buffer" })
    // 以xlsx格式下载该工作簿ArrayBuffer对象
    downloadFile(workbookArrBufObj, "波谱数据集_原格式.xlsx")
  // 若不用转置，即生成方便TensorFlow深度学习的数据格式
  } else {
    // 处理headerArr为headerAoa
    const headerAoa = headerArr.map((header) => {
      return [header]
    })
    // 生成标题表格
    const headerSheet = XLSX.utils.aoa_to_sheet(headerAoa)
    // 直接生成数据表格
    // aoa_to_sheet()方法接受Array-of-Array对象，FLoat64Array不行
    // 如遇FLoat64Array，一定要提前用Array.from()转成普通Array
    const dataSheet = XLSX.utils.aoa_to_sheet(spectrumPointArr)
    // 创建一个新的工作簿对象
    const workbook = XLSX.utils.book_new()
    // 将数据表添加到工作簿，该表名为"波谱数据集"
    XLSX.utils.book_append_sheet(workbook, dataSheet, "波谱数据集_处理", true)
    // 将标题表添加到工作簿，该表名为"数据索引"
    XLSX.utils.book_append_sheet(workbook, headerSheet, "数据序列索引", true)
    // 以工作簿生成ArrayBuffer对象
    const workbookArrBufObj = XLSX.write(workbook, { type: "buffer" })
    // 以xlsx格式下载该工作簿ArrayBuffer对象
    downloadFile(workbookArrBufObj, "波谱数据集_矩阵格式.xlsx")
  }
  // 隐藏加载圈
  uni.hideLoading()
} catch (error) {
  console.error("resultToDownload()报错: ", error)
  uni.hideLoading()
  uni.showModal({
    showCancel: false,
    title: "数据导出报错",
    content: error,
  })
}}

</script>
